/**
 * Copyright 2008-2009. Chongqing Communications Industry Services Co.,Ltd Information Technology Branch. All rights
 * reserved. <a>http://www.cqcis.com</a>
 */
package com.firebird.base.orm.hibernate;

import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.criterion.Conjunction;
import org.hibernate.criterion.CriteriaSpecification;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projection;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.hibernate.impl.CriteriaImpl;
import org.hibernate.metadata.ClassMetadata;
import org.hibernate.transform.Transformers;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;

import com.firebird.base.domain.Page;
import com.firebird.base.orm.HibernateQueryUtil;
import com.firebird.base.orm.SqlUtils;
import com.firebird.base.utils.BeanUtils;
import com.firebird.base.utils.GenericsUtils;

/**
 * @author qiuxj
 * @date 2009-11-14
 */
public class EntityHibernateDao<E> extends HibernateDaoSupport {

	  protected final Log log = LogFactory.getLog(super.getClass());
	  protected Class entityClass;

	  protected Class getEntityClass()
	  {
	    return this.entityClass;
	  }

	  public void setEntityClass(Class clazz) {
	    this.entityClass = clazz;
	  }

	  public EntityHibernateDao()
	  {
	    this.entityClass = GenericsUtils.getGenericClass(super.getClass());
	  }

	  public E get(Serializable id)
	  {
	    return (E) getHibernateTemplate().get(this.entityClass, id);
	  }

	  public List<E> list()
	  {
	    return getHibernateTemplate().loadAll(this.entityClass);
	  }

	  public Serializable create(Object o)
	  {
	    return super.getHibernateTemplate().save(o);
	  }

	  public void update(Object o)
	  {
	    super.getHibernateTemplate().update(o);
	  }

	  public void save(Object o)
	  {
	    getHibernateTemplate().merge(o);
	  }

	  public void remove(Object o)
	  {
	    getHibernateTemplate().delete(o);
	  }

	  public void removeById(Serializable id)
	  {
	    remove(get(id));
	  }

	  /** @deprecated */
	  public void evict(Object entity)
	  {
	    getHibernateTemplate().evict(entity);
	  }

	  public void flush()
	  {
	    getHibernateTemplate().flush();
	  }

	  public void saveAll(Collection c)
	  {
	    super.getHibernateTemplate().saveOrUpdateAll(c);
	  }

	  public void removeAll(Collection c)
	  {
	    super.getHibernateTemplate().deleteAll(c);
	  }

	  public void clear()
	  {
	    getHibernateTemplate().clear();
	  }

	  public int getCount(String hsql, Object[] values)
	  {
	    Object c = getHibernateTemplate().find(hsql, values).get(0);

	    if (c instanceof Integer) {
	      return ((Integer)c).intValue();
	    }

	    return 0;
	  }

	  public int getCount(Criteria criteria)
	  {
	    return ((Integer)criteria.setProjection(Projections.rowCount()).uniqueResult()).intValue();
	  }

	  public List find(String hql, Object[] values)
	  {
	    return getHibernateTemplate().find(hql, values);
	  }

	  public List find(String hql)
	  {
	    return getHibernateTemplate().find(hql);
	  }

	  public List find(String hql, Map param)
	  {
	    return getSession().createQuery(hql).setProperties(param).list();
	  }

	  public List<E> find(Map filter)
	  {
	    Criteria criteria = createCriteria(new Criterion[0]);
	    return find(criteria, filter);
	  }

	  public List<E> find(Criteria criteria, Map filter)
	  {
	    Assert.notNull(criteria);

	    filterCriteria(criteria, filter);
	    return criteria.list();
	  }

	  public List<E> findBy(String key, Object value)
	  {
	    Assert.hasText(key);
	    Criteria criteria = getSession().createCriteria(this.entityClass);
	    criteria.add(Restrictions.eq(key, value));
	    return criteria.list();
	  }

	  public List<E> findByLike(String key, String value)
	  {
	    Assert.hasText(key);
	    Criteria criteria = getSession().createCriteria(this.entityClass);
	    criteria.add(Restrictions.like(key, value, MatchMode.ANYWHERE));
	    return criteria.list();
	  }

	  public boolean isUnique(Object entity, String names)
	  {
	    Assert.hasText(names);
	    Criteria criteria = getSession().createCriteria(this.entityClass).setProjection(Projections.rowCount());
	    String[] nameList = names.split(",");
	    try
	    {
	      for (String name : nameList) {
	        criteria.add(Restrictions.eq(name, PropertyUtils.getProperty(entity, name)));
	      }

	      String idName = getIdName(this.entityClass);

	      Serializable id = getId(this.entityClass, entity);

	      if (id != null)
	        criteria.add(Restrictions.not(Restrictions.eq(idName, id)));
	    } catch (Exception e) {
	      ReflectionUtils.handleReflectionException(e);
	    }

	    return (((Integer)criteria.uniqueResult()).intValue() == 0);
	  }

	  public E getUniqueBy(String key, Object value)
	  {
	    Criteria criteria = getSession().createCriteria(getEntityClass());
	    criteria.add(Restrictions.eq(key, value));
	    return (E) criteria.uniqueResult();
	  }

	  /** @deprecated */
	  public E getFirst(String key, Object value)
	  {
	    Criteria criteria = getSession().createCriteria(getEntityClass());
	    criteria.add(Restrictions.eq(key, value));
	    return (E) criteria.setMaxResults(1).list();
	  }

	  public Query createQuery(String hql, Object[] values)
	  {
	    Assert.hasText(hql);
	    Query query = getSession().createQuery(hql);
	    for (int i = 0; i < values.length; ++i) {
	      query.setParameter(i, values[i]);
	    }
	    return query;
	  }

	  public Criteria createCriteria(Criterion[] criterions)
	  {
	    Criteria criteria = getSession().createCriteria(this.entityClass);
	    for (Criterion c : criterions) {
	      criteria.add(c);
	    }
	    return criteria;
	  }

	  public Criteria createCriteria(Map filterMap, Map likeMap, Map sortMap)
	  {
	    Criteria criteria = getSession().createCriteria(this.entityClass);

	    filterCriteria(criteria, filterMap);
	    likeCriteria(criteria, likeMap);
	    sortCriteria(criteria, sortMap);

	    return criteria;
	  }

	  public Page pagedQuery(Map filterMap, Map likeMap, Map sortMap, int pageNo, int pageSize)
	  {
	    return pagedQuery(createCriteria(filterMap, likeMap, sortMap), pageNo, pageSize);
	  }

	  public Page pagedSql(String sql, int pageNo, int pageSize)
	  {
	    return pagedSql(sql, pageNo, pageSize, -1);
	  }

	  public Page pageSql(String sql, int pageNo, int pageSize, int startIndex)
	  {
	    return pageSql(sql, pageNo, pageSize, startIndex, -1);
	  }

	  public Page pageSql(String sql, int pageNo, int pageSize, int startIndex, int totalCount)
	  {
	    Assert.hasText(sql);
	    Assert.isTrue(pageNo >= 1, "pageNo should start from 1");

	    if (totalCount == -1) {
	      String countQueryString = " select count (*) " + SqlUtils.removeSelect(SqlUtils.removeOrders(sql));
	      List countlist = getSession().createSQLQuery(countQueryString).list();
	      totalCount = ((BigDecimal)countlist.get(0)).intValue();
	    }

	    Query query = getSession().createSQLQuery(sql);
	    query.setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
	    List list = query.setFirstResult(startIndex).setMaxResults(pageSize).list();

	    return new Page(list, startIndex, totalCount, pageSize);
	  }

	  public Page pagedSql(String sql, int pageNo, int pageSize, int totalCount)
	  {
	    Assert.hasText(sql);
	    Assert.isTrue(pageNo >= 1, "pageNo should start from 1");

	    if (totalCount == -1) {
	      String countQueryString = " select count (*) " + SqlUtils.removeSelect(SqlUtils.removeOrders(sql));
	      List countlist = getSession().createSQLQuery(countQueryString).list();
	      totalCount = ((BigDecimal)countlist.get(0)).intValue();
	    }

	    int startIndex = Page.getStartOfPage(pageNo, pageSize);
	    Query query = getSession().createSQLQuery(sql);
	    query.setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
	    List list = query.setFirstResult(startIndex).setMaxResults(pageSize).list();

	    return new Page(list, startIndex, totalCount, pageSize);
	  }

	  public Page pagedQuery(String hql, int pageNo, int pageSize, Object[] values)
	  {
	    return pagedQuery(hql, pageNo, pageSize, -1, values);
	  }

	  public Page pagedQuery(String hql, int pageNo, int pageSize, int totalCount, Object[] values)
	  {
	    Assert.hasText(hql);
	    if (totalCount == -1) {
	      String countQueryString = " select count(*) " + SqlUtils.removeSelect(SqlUtils.removeOrders(hql));
	      List countlist = getHibernateTemplate().find(countQueryString, values);
	      totalCount = ((Long)countlist.get(0)).intValue();
	    }

	    if (totalCount < 1) {
	      return new Page();
	    }
	    int startIndex = Page.getStartOfPage(pageNo, pageSize);

	    Query query = createQuery(hql, values);
	    List list = query.setFirstResult(startIndex).setMaxResults(pageSize).list();

	    return new Page(list, startIndex, totalCount, pageSize);
	  }

	  public Page pagedQuery(String hql, int pageNo, int pageSize, Map param)
	  {
	    return pagedQuery(hql, pageNo, pageSize, -1, param);
	  }

	  public Page pagedQuery(String hql, int pageNo, int pageSize, int totalCount, Map param)
	  {
	    Assert.hasText(hql);
	    if (totalCount == -1) {
	      String countQueryString = " select count(*) " + SqlUtils.removeSelect(SqlUtils.removeOrders(hql));
	      List countlist = getSession().createQuery(countQueryString).setProperties(param).list();
	      totalCount = ((Long)countlist.get(0)).intValue();
	    }

	    if (totalCount < 1) {
	      return new Page();
	    }
	    int startIndex = Page.getStartOfPage(pageNo, pageSize);

	    Query query = getSession().createQuery(hql).setProperties(param);
	    List list = query.setFirstResult(startIndex).setMaxResults(pageSize).list();

	    return new Page(list, startIndex, totalCount, pageSize);
	  }

	  public Page pagedQuery(Criteria criteria, int pageNo, int pageSize)
	  {
	    List orderEntries;
	    Assert.notNull(criteria);
	    Assert.isTrue(pageNo >= 1, "pageNo should start from 1");
	    CriteriaImpl impl = (CriteriaImpl)criteria;

	    Projection projection = impl.getProjection();
	    try
	    {
	      orderEntries = (List)BeanUtils.forceGetProperty(impl, "orderEntries");
	      BeanUtils.forceSetProperty(impl, "orderEntries", new ArrayList());
	    } catch (Exception e) {
	      throw new InternalError(" Runtime Exception impossibility throw ");
	    }

	    int totalCount = 0;
	    try
	    {
	      totalCount = ((Integer)criteria.setProjection(Projections.rowCount()).uniqueResult()).intValue();
	    } catch (Exception e) {
	      System.out.println("E:" + e.getMessage());
	    }

	    criteria.setProjection(projection);
	    if (projection == null)
	      criteria.setResultTransformer(CriteriaSpecification.ROOT_ENTITY);
	    try
	    {
	      BeanUtils.forceSetProperty(impl, "orderEntries", orderEntries);
	    } catch (Exception e) {
	      throw new InternalError(" Runtime Exception impossibility throw ");
	    }

	    if (totalCount < 1) {
	      return new Page();
	    }
	    int startIndex = Page.getStartOfPage(pageNo, pageSize);

	    List list = criteria.setFirstResult(startIndex).setMaxResults(pageSize).list();

	    return new Page(list, startIndex, totalCount, pageSize);
	  }

	  protected void filterCriteria(Criteria criteria, Map filter)
	  {
	    if ((filter != null) && (!(filter.isEmpty()))) {
	      HibernateQueryUtil.changeStringToClassFieldTypeOfMap(this.entityClass, filter, Boolean.valueOf(true));
	      Conjunction conj = Restrictions.conjunction();
	      Iterator iter = filter.entrySet().iterator();
	      while (iter.hasNext()) {
	        Map.Entry me = (Map.Entry)iter.next();

	        if (me.getKey().toString().indexOf(">=") > 0) {
	          conj.add(Restrictions.ge(((String)me.getKey()).replace(">=", ""), me.getValue()));
	        }
	        else if (me.getKey().toString().indexOf("<=") > 0) {
	          conj.add(Restrictions.le(((String)me.getKey()).replace("<=", ""), me.getValue()));
	        }
	        else if (me.getKey().toString().indexOf(">") > 0) {
	          conj.add(Restrictions.gt(((String)me.getKey()).replace(">", ""), me.getValue()));
	        }
	        else if (me.getKey().toString().indexOf("<") > 0) {
	          conj.add(Restrictions.lt(((String)me.getKey()).replace("<", ""), me.getValue()));
	        }
	        else if (me.getKey().toString().indexOf("!=") > 0) {
	          conj.add(Restrictions.ne(((String)me.getKey()).replace("!=", ""), me.getValue()));
	        }
	        else
	        {
	          conj.add(Restrictions.eq((String)me.getKey(), me.getValue())); }
	      }
	      criteria.add(conj);
	    }
	  }

	  protected void likeCriteria(Criteria criteria, Map filter)
	  {
	    if ((filter != null) && (!(filter.isEmpty()))) {
	      Set keys = filter.keySet();
	      for (Iterator localIterator = keys.iterator(); localIterator.hasNext(); ) { Object key = localIterator.next();
	        String value = (String)filter.get(key);
	        if (StringUtils.isNotBlank(value))
	          criteria.add(Restrictions.like((String)key, value, MatchMode.ANYWHERE));
	      }
	    }
	  }

	  protected void sortCriteria(Criteria criteria, Map sortMap)
	  {
	    if ((sortMap == null) || (sortMap.isEmpty()))
	      return;
	    for (Iterator localIterator = sortMap.keySet().iterator(); localIterator.hasNext(); ) { Object o = localIterator.next();
	      String fieldName = o.toString();
	      String orderType = sortMap.get(fieldName).toString();

	      if (fieldName.indexOf(46) != -1) {
	        String alias = StringUtils.substringBefore(fieldName, ".");
	        criteria.createAlias(alias, alias);
	      }

	      if ("asc".equalsIgnoreCase(orderType))
	        criteria.addOrder(Order.asc(fieldName));
	      else
	        criteria.addOrder(Order.desc(fieldName));
	    }
	  }

	  public Serializable getId(Class clazz, Object entity)
	    throws NoSuchMethodException, IllegalAccessException, InvocationTargetException
	  {
	    Assert.notNull(entity);
	    Assert.notNull(clazz);
	    return ((Serializable)PropertyUtils.getProperty(entity, getIdName(clazz)));
	  }

	  public String getIdName(Class clazz)
	  {
	    Assert.notNull(clazz);
	    ClassMetadata meta = getSessionFactory().getClassMetadata(clazz);
	    Assert.notNull(meta, "Class " + clazz + " not define in hibernate session factory.");
	    String idName = meta.getIdentifierPropertyName();
	    Assert.hasText(idName, clazz.getSimpleName() + " has no identifier property define.");
	    return idName;
	  }
	
}
